/**
 * Module dependencies.
 */
const _counter = require("passthrough-counter");
const humanize = require("humanize-number");
const bytes = require("bytes");
const chalk = require("chalk");
const util = require("./../util/util");
const _ = require("lodash");

/**
 * Expose logger.
 */

module.exports = dev;

/**
 * Color map.
 */

const colorCodes = {
  7: "magenta",
  5: "red",
  4: "yellow",
  3: "cyan",
  2: "green",
  1: "green",
  0: "yellow"
};

/**
 * Development logger.
 */

function dev(opts) {
  return async function logger(ctx, next) {
    // request
    const start = Date.now();
    console.log(
      "  " +
        chalk.gray("<--") +
        " " +
        chalk.bold("%s") +
        " " +
        chalk.gray("%s"),
      ctx.method,
      ctx.originalUrl
    );

    try {
      await next();
    } catch (err) {
      // log uncaught downstream errors
      log(ctx, start, null, err);
      throw err;
    }

    // calculate the length of a streaming response
    // by intercepting the stream with a counter.
    // only necessary if a content-length header is currently not set.
    const length = ctx.response.length;
    const body = ctx.body;
    let counter;
    /* eslint-disable eqeqeq */
    if (length == null && body && body.readable) {
      ctx.body = body.pipe((counter = _counter())).on("error", ctx.onerror);
    }
    /* eslint-disable eqeqeq */

    // log when the response is finished or closed,
    // whichever happens first.
    const res = ctx.res;

    const onfinish = done.bind(null, "finish");
    const onclose = done.bind(null, "close");

    res.once("finish", onfinish);
    res.once("close", onclose);

    function done(event) {
      res.removeListener("finish", onfinish);
      res.removeListener("close", onclose);
      log(ctx, start, counter ? counter.length : length, null, event);
    }
  };
}

/**
 * Log helper.
 */

function log(ctx, start, len, err, event) {
  // get the status code of the response
  const status = err ? err.status || 500 : ctx.status || 404;

  // set the color of the status code;
  const s = (status / 100) | 0;
  const color = colorCodes.hasOwnProperty(s) ? colorCodes[s] : 0;

  /* eslint-disable eqeqeq */
  // get the human readable response length
  let length;
  if (~[204, 205, 304].indexOf(status)) {
    length = "";
  } else if (len == null) {
    length = "-";
  } else {
    length = bytes(len).toLowerCase();
  }
  /* eslint-disable eqeqeq */

  const upstream = err
    ? chalk.red("xxx")
    : event === "close" ? chalk.yellow("-x-") : chalk.gray("-->");
  const uptime = time(start);
  const log =
    "  " +
    upstream +
    " " +
    chalk.bold(ctx.method) +
    " " +
    chalk.gray(ctx.originalUrl) +
    " " +
    chalk[color](status) +
    " " +
    chalk.gray(uptime) +
    " " +
    chalk.gray(length);
  console.log(log);

  if (!_.isEmpty(ctx.baas)) {
    const rlog = {
      baas_id: ctx.baas.id,
      method: ctx.method,
      url: ctx.url,
      status: status,
      time: uptime,
      length
    };

    BaaS.redis.rpushAsync(
      util.getVersionKey("baas_log_request"),
      JSON.stringify(rlog)
    );

    // 发布订阅
    BaaS.redis.publish(
      util.getVersionKey("log"),
      JSON.stringify(
        Object.assign(ctx.baas, {
          message: log
        })
      )
    );
  }
}

/**
 * Show the response time in a human readable format.
 * In milliseconds if less than 10 seconds,
 * in seconds otherwise.
 */

function time(start) {
  const delta = Date.now() - start;
  return humanize(
    delta < 10000 ? delta + "ms" : Math.round(delta / 1000) + "s"
  );
}
